package com.shadowtorcher.TableRolePlayingSystem.controller.WebSocket;

import com.shadowtorcher.TableRolePlayingSystem.DataStructure.DataProcessor;
import com.shadowtorcher.TableRolePlayingSystem.DataStructure.DataSegment;
import com.shadowtorcher.TableRolePlayingSystem.util.Logger;

import java.util.*;

public class CommandProcessor {
    public static void doProcess(DataSegment dataSegment, WebSocketUser webSocketUser, WebSocketRoom webSocketRoom) {
        Logger.debug(CommandProcessor.class, "处理消息:", dataSegment, "用户:", webSocketUser.getUserName());
        String type = dataSegment.getString("type");
        switch (type) {
            case "chat": {
                DataSegment ans = new DataSegment();
                ans.put("type", "chat");
                ans.put("from", webSocketUser.getUserName());
                ans.put("data", tool.webEscape(dataSegment.getString("data")));
                int serial = SaveProcessor.saveLog(webSocketUser.getRoomID(), ans.toString());
                ans.put("serial", String.valueOf(serial));
                WebSocketHandler.sendAll(ans, webSocketRoom);
                break;
            }
            case "instruct": {
                String[] message = dataSegment.getString("data").split(" ");
                String command = message[0];
                String[] other = Arrays.copyOfRange(message, 1, message.length);
                instructProcessor(webSocketUser, webSocketRoom, command, other);
                break;
            }
            case "online": {
                String what = dataSegment.getString("what");
                DataSegment ans = new DataSegment();
                switch (what) {
                    case "current":
                        ans.put("type", "online");
                        ans.put("what", "current");
                        ans.put("serial", String.valueOf(SaveProcessor.getCurrentByRoom(webSocketUser.getRoomID())));
                        WebSocketHandler.send(ans, webSocketUser);
                        break;
                    case "history":
                        String serial = dataSegment.getString("serial");
                        // TODO 空处理
                        String data = SaveProcessor.getLogByRoomAndSerial(webSocketUser.getRoomID(), Integer.parseInt(serial)).getData();
                        DataSegment log = DataProcessor.buildSegment(data);
                        log.put("serial", serial);
                        WebSocketHandler.send(log, webSocketUser);
                        break;
                }
                break;
            }
            case "camera": {
                String what = dataSegment.getString("what");
                switch (what) {
                    case "save":
                        String map = dataSegment.getString("data");
                        webSocketRoom.updateMapData(map);
                        break;
                    case "hotupdate":
                        WebSocketHandler.sendAllExcept(dataSegment, webSocketRoom, webSocketUser);
                        break;
                }
                break;
            }
            case "status": {
                if (dataSegment.getString("data").equals("update")) {
                    DataSegment status = new DataSegment();
                    status.put("type", "status");
                    status.put("where", "all");
                    if (webSocketUser.getUserData() != null) {
                        status.put("to", webSocketUser.getUserSegment());
                    } else {
                        status.put("to", new DataSegment());
                    }
                    WebSocketHandler.send(status, webSocketUser);
                }
                break;
            }
        }
    }

    private static void instructProcessor(WebSocketUser user, WebSocketRoom room, String command, String[] other) {
        switch (command) {
            case "me":
                me(user, room, other);
                break;
            case "bg":
                bg(user, room, other);
                break;
            case "event":
                event(user, room, other);
                break;
            case "save":
                save(user, room, other);
                break;
            case "buff":
                buff(user, room, other);
                break;
            case "change":
                change(user, room, other);
                break;
            case "checkstatus":
                checkstatus(user, room, other);
                break;
            case "roll":
                roll(user, room, other);
                break;
            case "rolld":
                rolld(user, room, other);
                break;
            case "move":
                move(user, room, other);
                break;
            case "say":
                say(user, room, other);
                break;
            case "help":
            default:
                help(user, room, other);
                break;
        }
    }

    private static void say(WebSocketUser webSocketUser, WebSocketRoom webSocketRoom, String... strings) {
        if (strings.length >= 2) { // 至少需要 1 个参数
            String name = strings[0];
            DataSegment ans = new DataSegment();
            ans.put("type", "chat");
            ans.put("from", name);
            ans.put("data", tool.join(strings, 1));
            int serial = SaveProcessor.saveLog(webSocketUser.getRoomID(), ans.toString());
            ans.put("serial", String.valueOf(serial));
            WebSocketHandler.sendAll(ans, webSocketRoom);
        }
    }

    // me [字符串] | 使用 me 指令说话时会变成第三人称转述. | /me 离开了 => *Sasasu* 离开了
    private static void me(WebSocketUser webSocketUser, WebSocketRoom webSocketRoom, String... strings) {
        DataSegment dataSegment = new DataSegment();
        dataSegment.put("type", "instruct");
        if (strings.length >= 1) { // 至少需要 1 个参数
            String s = "<em>" + webSocketUser.getUserName() + "</em>&nbsp;" + tool.join(strings);
            dataSegment.put("data", s);
            int serial = SaveProcessor.saveLog(webSocketUser.getRoomID(), dataSegment.toString());
            dataSegment.put("serial", String.valueOf(serial));
            WebSocketHandler.sendAll(dataSegment, webSocketRoom);
        } else {
            dataSegment.put("data", "" /*为了 IDE 排版 */ +
                    "格式 : me [字符串] <br />" +
                    "使用 me 指令说话时会变成第三人称转述. <br />" +
                    "/me 离开了 => <em>" + webSocketUser.getUserName() + "</em> 离开了");
            dataSegment.put("color", "light");
            WebSocketHandler.send(dataSegment, webSocketUser);
        }
    }

    // bg <背景描述> | PC 进入一个区域后进行特定的背景描述 | /bg 你们来到了河谷，面前的峭壁阻止了你们的前进，你们随时会被敌人的追兵发现。
    private static void bg(WebSocketUser webSocketUser, WebSocketRoom webSocketRoom, String... strings) {
        DataSegment dataSegment = new DataSegment();
        dataSegment.put("type", "instruct");
        if (strings.length >= 1) { // 至少需要 1 个参数
            String s = tool.join(strings);
            dataSegment.put("data", s);
            dataSegment.put("color", "info");
            int serial = SaveProcessor.saveLog(webSocketUser.getRoomID(), dataSegment.toString());
            dataSegment.put("serial", String.valueOf(serial));
            WebSocketHandler.sendAll(dataSegment, webSocketRoom);
        } else {
            dataSegment.put("data", "" /*为了 IDE 排版 */ +
                    "格式 : bg &lt;背景描述&gt; <br />" +
                    "PC 进入一个区域后进行特定的背景描述 <br />" +
                    "/bg 你们来到了河谷，面前的峭壁阻止了你们的前进，你们随时会被敌人的追兵发现。");
            dataSegment.put("color", "light");
            WebSocketHandler.send(dataSegment, webSocketUser);
        }
    }

    // event <Object> <事件描述> | 以系统人称叙述事件 | /event Sasasu 决定攀爬前面的峭壁，进行攀爬技能检定
    private static void event(WebSocketUser webSocketUser, WebSocketRoom webSocketRoom, String... strings) {
        DataSegment dataSegment = new DataSegment();
        dataSegment.put("type", "instruct");
        if (strings.length >= 2) { // 至少需要 2 个参数
            strings[0] = "<em>" + strings[0] + "</em>";
            String s = tool.join(strings, 1);
            dataSegment.put("data", s);
            dataSegment.put("color", "info");
            int serial = SaveProcessor.saveLog(webSocketUser.getRoomID(), dataSegment.toString());
            dataSegment.put("serial", String.valueOf(serial));
            WebSocketHandler.sendAll(dataSegment, webSocketRoom);
        } else {
            dataSegment.put("data", "" /*为了 IDE 排版 */ +
                    "格式 : event &lt;Object&gt; &lt;事件描述&gt; <br />" +
                    "以系统人称叙述事件 <br />" +
                    "/event &ltPlayer> 决定攀爬前面的峭壁，进行攀爬技能检定");
            dataSegment.put("color", "light");
            int serial = SaveProcessor.saveLog(webSocketUser.getRoomID(), dataSegment.getString("data"));
            dataSegment.put("serial", String.valueOf(serial));
            WebSocketHandler.send(dataSegment, webSocketUser);
        }
    }

    // buff <Object> <效果描述> <受影响属性条目 1> <改变值> [受属性条目 2] [改变值] ...
    // 一个目标因为特殊状态改变了属性，特殊状态消失后可以撤回效果影响，可以影响多个属性。Object 字段为 All 表示对当前所有 PC 进行更改
    // /buff Sasasu 膝盖上中了一个毒箭 Dex -2 Int -1
    private static void buff(WebSocketUser webSocketUser, WebSocketRoom webSocketRoom, String... strings) {
        DataSegment dataSegment = new DataSegment();
        dataSegment.put("type", "instruct");
        if (strings.length < 4) { // 至少要 4 个参数
            dataSegment.put("data", "" /*为了 IDE 排版 */ +
                    "格式 : buff &lt;Object&gt; &lt;效果描述&gt; &lt;受影响属性条目 1&gt; &lt;改变值&gt; [受属性条目 2] [改变值] ... <br />" +
                    "一个目标因为特殊状态改变了属性，特殊状态消失后可以撤回效果影响，可以影响多个属性。Object 字段为 All 表示对当前所有 PC 进行更改 <br />" +
                    "/buff Player 膝盖上中了一个毒箭 Dex -2 Int -1");
            dataSegment.put("color", "light");
            WebSocketHandler.send(dataSegment, webSocketUser);
            return;
        }
        String userName = strings[0];
        String description = strings[1];
        ArrayList<WebSocketUser> players = tool.getUserByName(userName, webSocketRoom);
        try {
            HashMap<String, Integer> kv = tool.getChange(strings);
            tool.processChange(userName, description, players, kv, dataSegment);
        } catch (RuntimeException e) {
            dataSegment.put("data", e.getMessage());
            dataSegment.put("color", "light");
            WebSocketHandler.send(dataSegment, webSocketUser);
            return;
        }
        int serial = SaveProcessor.saveLog(webSocketUser.getRoomID(), dataSegment.toString());
        dataSegment.put("serial", String.valueOf(serial));
        WebSocketHandler.sendAll(dataSegment, webSocketRoom);
    }

    // change  <Object> <效果描述> <属性条目 1> <改变值> [属性条目 2] [改变值] ...
    // 改变指定对象的属性值，一次性操作，无需记录撤回，可以改变多个属性。Object 字段为 All 表示对当前所有 PC 进行更改
    // /change Sasasu 攀爬检定大失败跌落脑部受损 hp -10 Int -1
    private static void change(WebSocketUser webSocketUser, WebSocketRoom webSocketRoom, String... strings) {
        DataSegment dataSegment = new DataSegment();
        dataSegment.put("type", "instruct");
        if (strings.length < 4) { // 至少要 4 个参数
            dataSegment.put("data", "" /*为了 IDE 排版 */ +
                    "格式 : change  &lt;Object&gt; &lt;效果描述&gt; &lt;属性条目 1&gt; &lt;改变值&gt; [属性条目 2] [改变值] ... <br />" +
                    "改变指定对象的属性值，一次性操作，无需记录撤回，可以改变多个属性。Object 字段为 All 表示对当前所有 PC 进行更改 <br />" +
                    "/change Player 攀爬检定大失败跌落脑部受损 hp -10 Int -1");
            dataSegment.put("color", "light");
            WebSocketHandler.send(dataSegment, webSocketUser);
            return;
        }
        buff(webSocketUser, webSocketRoom, strings);
    }

    // save | 记录游戏状态信息 | /save
    private static void save(WebSocketUser webSocketUser, WebSocketRoom webSocketRoom, String... strings) {
        // 在 WebSocketRoom 里关联地图, 来保存
        DataSegment dataSegment = new DataSegment();
        dataSegment.put("type", "instruct");
        dataSegment.put("data", "游戏已保存");
        dataSegment.put("color", "info");
        // 让用户发送
        DataSegment save = new DataSegment();
        save.put("type", "camera");
        save.put("what", "save");
        WebSocketHandler.send(save, webSocketUser);
        WebSocketHandler.sendAll(dataSegment, webSocketRoom);
    }

    // checkstatus <palyer> |返回 [player] 状态数值链接 | /checkstatus Sasasu
    private static void checkstatus(WebSocketUser webSocketUser, WebSocketRoom webSocketRoom, String... strings) {
        DataSegment dataSegment = new DataSegment();
        dataSegment.put("type", "instruct");
        if (strings.length >= 1) { // 至少要 4 个参数
            String player = strings[0];
            // TODO 在这处理
        } else {
            dataSegment.put("data", "" /*为了 IDE 排版 */ +
                    "格式 : checkstatus &lt;palyer&gt;  <br />" +
                    "返回 Player 状态数值链接 <br />" +
                    "/checkstatus Player");
            dataSegment.put("color", "light");
            WebSocketHandler.send(dataSegment, webSocketUser);
        }
    }

    // roll <骰子数> <骰子面数> [调整值] | 投骰子，骰子面数前加 d  | /roll 4 d6 +3
    private static void roll(WebSocketUser webSocketUser, WebSocketRoom webSocketRoom, String... strings) {
        DataSegment dataSegment = new DataSegment();
        dataSegment.put("type", "instruct");
        if (strings.length >= 2) { // 至少要 4 个参数
            String number = strings[0];
            String surface = strings[1];
            String adjust = 3 <= strings.length ? strings[2] : "0";
            try {
                int numberI = Integer.parseInt(number);
                int surfaceI = Integer.parseInt(surface.substring(1));
                int adjustI = Integer.parseInt(adjust);
                StringBuilder ans = new StringBuilder();
                ans.append("<em>").append(webSocketUser.getUserName()).append("</em>").append(" 投掷 ").append(surface).append("*").append(number);
                ans.append("<br />");
                ans.append("(");
                StringJoiner stringJoiner = new StringJoiner(",");
                int ansI = 0;
                while (numberI-- > 0) {
                    int rand = tool.rand(surfaceI);
                    ansI += rand;
                    stringJoiner.add(String.valueOf(rand));
                }
                ansI += adjustI;
                String ansS = ansI >= 0 ? "+" + String.valueOf(ansI) : String.valueOf(ansI);
                adjust = adjustI >= 0 ? "+" + adjust : adjust;
                ans.append(stringJoiner.toString()).append(")").append(adjust).append(" = ").append(ansS);
                dataSegment.put("data", ans.toString());
                int serial = SaveProcessor.saveLog(webSocketUser.getRoomID(), dataSegment.toString());
                dataSegment.put("serial", String.valueOf(serial));
                WebSocketHandler.sendAll(dataSegment, webSocketRoom);
            } catch (Exception e) {
                dataSegment.put("data", "参数转换出错 number = " + number + " surface = " + surface + " adjust = " + adjust);
                dataSegment.put("color", "danger");
                WebSocketHandler.send(dataSegment, webSocketUser);
            }
        } else {
            dataSegment.put("data", "" /*为了 IDE 排版 */ +
                    "格式 : roll &lt;骰子数&gt; &lt;骰子面数&gt; [调整值] <br />" +
                    "投骰子，骰子面数前加 d <br />" +
                    "/roll 4 d6 +3");
            dataSegment.put("color", "light");
            int serial = SaveProcessor.saveLog(webSocketUser.getRoomID(), dataSegment.toString());
            dataSegment.put("serial", String.valueOf(serial));
            WebSocketHandler.send(dataSegment, webSocketUser);
        }
    }

    // rolld <骰子数> <骰子面数> [调整值] | 投骰子，骰子面数前加 d 暗投版本  | /roll 4 d6 +3
    private static void rolld(WebSocketUser webSocketUser, WebSocketRoom webSocketRoom, String... strings) {
        DataSegment dataSegment = new DataSegment();
        DataSegment allDataSegment = new DataSegment();
        allDataSegment.put("data", webSocketUser.getUserName() + " 从屏幕外抓出一把骰子投下");
        int serial = SaveProcessor.saveLog(webSocketUser.getRoomID(), allDataSegment.toString());
        allDataSegment.put("serial", String.valueOf(serial));
        dataSegment.put("type", "instruct");
        allDataSegment.put("type", "instruct");
        if (strings.length >= 2) { // 至少要 4 个参数
            String number = strings[0];
            String surface = strings[1];
            String adjust = 3 <= strings.length ? strings[2] : "0";
            try {
                int numberI = Integer.parseInt(number);
                int surfaceI = Integer.parseInt(surface.substring(1));
                int adjustI = Integer.parseInt(adjust);
                StringBuilder ans = new StringBuilder();
                ans.append("投掷 ").append(surface).append("*").append(number);
                ans.append("<br />");
                ans.append("(");
                StringJoiner stringJoiner = new StringJoiner(",");
                int ansI = 0;
                while (numberI-- > 0) {
                    int rand = tool.rand(surfaceI);
                    ansI += rand;
                    stringJoiner.add(String.valueOf(rand));
                }
                ansI += adjustI;
                String ansS = ansI >= 0 ? "+" + String.valueOf(ansI) : String.valueOf(ansI);
                adjust = adjustI >= 0 ? "+" + adjust : adjust;
                ans.append(stringJoiner.toString()).append(")").append(adjust).append(" = ").append(ansS);
                dataSegment.put("data", ans.toString());
                WebSocketHandler.send(dataSegment, webSocketUser);
                WebSocketHandler.sendAllExcept(allDataSegment, webSocketRoom, webSocketUser);
            } catch (Exception e) {
                dataSegment.put("data", "参数转换出错 number = " + number + " surface = " + surface + " adjust = " + adjust);
                dataSegment.put("color", "danger");
                WebSocketHandler.send(dataSegment, webSocketUser);
            }
        } else {
            dataSegment.put("data", "" /*为了 IDE 排版 */ +
                    "格式 : rolld &lt;骰子数&gt; &lt;骰子面数&gt; [调整值] <br />" +
                    "投骰子，骰子面数前加 d 暗投版本 <br />" +
                    "/roll 4 d6 +3");
            dataSegment.put("color", "light");
            WebSocketHandler.send(dataSegment, webSocketUser);
        }
    }

    // TODO 地图部分
    // move <up\|down\|left\|right> <距离> [Object] | 试图移动指定角色角色。PC 可省略 [Object] 代表移动自身|/move Sasasu up 10
    private static void move(WebSocketUser webSocketUser, WebSocketRoom webSocketRoom, String... strings) {
        DataSegment dataSegment = new DataSegment();
        dataSegment.put("type", "instruct");
        if (strings.length >= 2) { // 至少要 2 个参数
            String direction = strings[0];
            String distance = strings[1];
            String player;
            if (strings.length >= 3) {
                player = strings[2];
            } else {
                player = webSocketUser.getUserName();
            }
        } else {
            dataSegment.put("data", "" /*为了 IDE 排版 */ +
                    "格式 : move &lt;up|down|left|right&gt; &lt;距离&gt; [Object] <br />" +
                    "试图移动指定角色角色。PC 可省略 [Object] 代表移动自身 <br />" +
                    "/move Player up 10");
            dataSegment.put("color", "light");
            WebSocketHandler.send(dataSegment, webSocketUser);
        }
    }

    private static void help(WebSocketUser webSocketUser, WebSocketRoom webSocketRoom, String... strings) {
        DataSegment dataSegment = new DataSegment();
        dataSegment.put("type", "instruct");
        dataSegment.put("data", "" /*为了 IDE 排版 */ +
                "<div class='text-left'>" +
                "可用指令 : <br >" +
                "<pre>" +
                "change  <Object> <效果描述> <属性条目 1> <改变值> [属性条目 2] [改变值] ...  <br />" +
                "buff <Object> <效果描述> <受影响属性条目 1> <改变值> [受属性条目 2] [改变值] ... <br />" +
                "bg <背景描述> <br />" +
                "save <br />" +
                "move [Object] <up\\|down\\|left\\|right> [距离] <br />" +
                "checkstatus <palyer> <br />" +
                "roll <骰子数> <骰子面数> [调整值] <br />" +
                "rolld <骰子数> <骰子面数> [调整值] <br />" +
                "me [字符串] <br />" +
                "event <Object> <事件描述> <br />" +
                "</pre>" +
                "</div>");
        dataSegment.put("color", "light");
        WebSocketHandler.send(dataSegment, webSocketUser);
    }

    private static class tool {
        static final Random rand = new Random();
        static final Set<String> availableAttribute = new HashSet<String>() {{
            add("HP");
            add("LV");
            add("XP");
            add("STR");
            add("DEX");
            add("CON");
            add("INT");
            add("WIS");
            add("CHA");
        }};

        static String webEscape(String string) {
            return string.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
        }

        static int rand(int range) {
            return rand.nextInt(range + 1) + 1;
        }

        static String join(String[] strings) {
            return join(strings, 0);
        }

        static String join(String[] strings, int start) {
            return join(strings, start, "&nbsp;");
        }

        private static String join(String[] strings, String s) {
            return join(strings, 0, s);
        }

        private static String join(String[] strings, int start, String s) {
            StringJoiner stringJoiner = new StringJoiner(s);
            for (int i = 0; i < strings.length; i++) {
                String tmp;
                if (i < start) {
                    tmp = strings[i];
                } else {
                    tmp = webEscape(strings[i]);
                }
                stringJoiner.add(tmp);
            }
            return stringJoiner.toString();
        }

        private static boolean isAvailableAttribute(String attribute) {
            return availableAttribute.contains(attribute);
        }

        static ArrayList<WebSocketUser> getUserByName(String name, WebSocketRoom webSocketRoom) {
            ArrayList<WebSocketUser> players;
            if (name.equals("All")) {
                players = new ArrayList<>(webSocketRoom.getAllUser());
            } else {
                players = new ArrayList<>();
                WebSocketUser webSocketUser = webSocketRoom.getUser(name);
                if (webSocketUser != null && !webSocketUser.isDm()) {
                    players.add(webSocketUser);
                }
            }
            return players;
        }

        private static String nameJoiner(ArrayList<WebSocketUser> arrayList) {
            StringBuilder s = new StringBuilder();
            for (WebSocketUser user : arrayList) {
                s.append(user.getUserName()).append(" ");
            }
            if (arrayList.size() > 1) {
                s.append("一行人 ");
            }
            return s.toString();
        }

        static HashMap<String, Integer> getChange(String... strings) {
            HashMap<String, Integer> kv = new HashMap<>();
            for (int i = 2; i < strings.length; i += 2) {
                String items = strings[i].toUpperCase();
                if (!isAvailableAttribute(items)) {
                    throw new RuntimeException(items + " 不存在");
                }
                Integer value = Integer.parseInt(strings[i + 1]);
                kv.putIfAbsent(items, value);
            }
            return kv;
        }

        static void processChange(String userName, String description, ArrayList<WebSocketUser> players, HashMap<String, Integer> change, DataSegment dataSegment) {
            if (players.size() == 0) {
                throw new RuntimeException(userName + " 没有匹配到任何人");
            }
            StringBuilder stringBuilder = new StringBuilder();
            for (HashMap.Entry<String, Integer> e : change.entrySet()) {
                stringBuilder.append(e.getKey()).append(" ").append(e.getValue() >= 0 ? "+" + e.getValue().toString() : e.getValue().toString()).append(",");
                for (WebSocketUser user : players) {
                    if (user.isDm())
                        continue;
                    String tmp = user.getUserSegment().getString(e.getKey());
                    int i = Integer.parseInt(tmp) + e.getValue();
                    user.getUserSegment().put(e.getKey(), String.valueOf(i));
                    user.updateUserData();
                    sendChange(e.getKey(), i, user);
                }
            }
            dataSegment.put("data", nameJoiner(players) + " " + description + " " + stringBuilder.toString());
        }

        static void sendChange(String where, int to, WebSocketUser user) {
            DataSegment segment = new DataSegment();
            segment.put("type", "status");
            segment.put("where", where);
            segment.put("to", String.valueOf(to));
            WebSocketHandler.send(segment, user);
        }
    }
}
